// GLObject3D.cpp

#include "GLObject3D.h"

namespace Voltron
{
	GLObject3D::GLObject3D()
	{
		// APPARENTLY, I need a newer Boost lib, so I'm commenting this out for now.
		//this->_uuid = boost::uuids::random_generator()();

		this->_vao[0] = 0;
		//this->_vbo = new std::map<string, GLuint>();
		this->_vbo[GLObject3D::POSITION] = 0;
		this->_vbo[GLObject3D::COLOR] = 0;
		this->_vbo[GLObject3D::NORMAL] = 0;
		this->_vbo[GLObject3D::FACE] = 0;
		this->_vbo[GLObject3D::TEXTURE] = 0;
	}

	GLObject3D::GLObject3D(int quantityVerts)
	{
		GLObject3D();
//		this->_positionVerts = new vector<vec4>(quantityVerts);
//		this->_colorVerts = new vector<vec4>(quantityVerts);
		this->_positionVerts.resize(quantityVerts);
		this->_colorVerts.resize(quantityVerts);
		this->_quantityVerts = quantityVerts;
	}

	GLObject3D::GLObject3D(int quantityVerts, vec4 positionVerts[], vec4 colorVerts[], GLuint shaderprogram)
	{
		GLObject3D();


		//this->_positionVerts = new vector<vec4>(quantityVerts);
		this->_positionVerts.insert(_positionVerts.end(), positionVerts, positionVerts+quantityVerts-1);
		//this->_colorVerts = new vector<vec4>(quantityVerts);
		this->_colorVerts.insert(_colorVerts.end(), colorVerts, colorVerts+quantityVerts-1);
		this->_quantityVerts = quantityVerts;
		initBuffers(shaderprogram);
	}

	GLObject3D::GLObject3D(int quantityVerts, vec4 positionVerts[], vec4 colorVerts[], vec4 selectionColor, GLuint shaderprogram)
	{
		GLObject3D();


		//this->_positionVerts = new vector<vec4>(quantityVerts);
		this->_positionVerts.insert(_positionVerts.end(), positionVerts, positionVerts+quantityVerts-1);
		//this->_colorVerts = new vector<vec4>(quantityVerts);
		this->_colorVerts.insert(_colorVerts.end(), colorVerts, colorVerts+quantityVerts-1);
		this->_quantityVerts = quantityVerts;
        this->_selectionColor = selectionColor;

		initBuffers(shaderprogram);
        }

	GLObject3D::~GLObject3D()
	{
	}

	// Public members
	void GLObject3D::attachVertexPositionShader(GLuint shaderprogram)
	{
		makeActiveObject();

		glBindBuffer(GL_ARRAY_BUFFER, _vbo[GLObject3D::POSITION]);

		glUseProgram(shaderprogram);

	    // Initialize the vertex position attribute from the vertex shader.  When
	    // the shader and the program are linked, a table is created for the shader
	    // variables.  Here, we get the index of the vPosition variable.
	    GLuint vPositionloc = glGetAttribLocation(shaderprogram, "vPosition");

	    // We want to set this with an array!
	    glEnableVertexAttribArray(vPositionloc);

	    // We map it to this offset in our current buffer (VBO) So, our position
	    // data is going into loc and contains 2 floats.  The parameters to this
	    // function are the index for the shader variable, the number of components,
	    // the type of the data, a boolean for whether or not this data is
	    // normalized (0--1), stride (or byte offset between consective attributes),
	    // and a pointer to the first component.  Note that BUFFER_OFFSET is a macro
	    // defined in the Angel.h file.
	    glVertexAttribPointer(vPositionloc, 4, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));
	}

	void GLObject3D::attachVertexColorShader(GLuint shaderprogram)
	{
		makeActiveObject();

		glBindBuffer(GL_ARRAY_BUFFER, _vbo[GLObject3D::COLOR]);

		glUseProgram(shaderprogram);

	    GLuint vColor = glGetAttribLocation(shaderprogram, "vColor");
	    glEnableVertexAttribArray(vColor);
	    glVertexAttribPointer(vColor, 4, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));

	}

	void GLObject3D::attachNormalShader(GLuint shaderprogram)
	{
		makeActiveObject();

		glBindBuffer(GL_ARRAY_BUFFER, _vbo[GLObject3D::NORMAL]);

		glUseProgram(shaderprogram);

		GLuint vNormal = glGetAttribLocation(shaderprogram, "vNormal" );
		glEnableVertexAttribArray( vNormal );
		glVertexAttribPointer(vNormal, 4, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));

	}

        /*
         * Figure out better way to do this.
         * For now it's in the main
	 */
	void GLObject3D::initSelectionColor(GLuint shaderprogram)
        {

            SelectColorRLoc = glGetUniformLocation(shaderprogram,"selectionColorR");
            SelectColorGLoc = glGetUniformLocation(shaderprogram,"selectionColorG");
            SelectColorBLoc = glGetUniformLocation(shaderprogram,"selectionColorB");
            SelectColorALoc = glGetUniformLocation(shaderprogram,"selectionColorA");
            glUniform1i(SelectColorRLoc, selectionColorR);
            glUniform1i(SelectColorGLoc, selectionColorG);
            glUniform1i(SelectColorBLoc, selectionColorB);
            glUniform1i(SelectColorALoc, selectionColorA);

            SelectFlagLoc = glGetUniformLocation(shaderprogram, "flag");
            glUniform1i(SelectFlagLoc, flag);
        }

        void GLObject3D::syncSelectionColor(){
            makeActiveObject();

            glUniform1i(SelectColorRLoc,_selectionColor[0]);
            glUniform1i(SelectColorGLoc,_selectionColor[1]);
            glUniform1i(SelectColorBLoc,_selectionColor[2]);
            glUniform1i(SelectColorALoc,_selectionColor[3]);
            glUniform1i(SelectFlagLoc, 1);
        }

	void GLObject3D::initBuffers()
	{
		if (_vao[0] == 0)
			glGenVertexArrays(1, _vao);

		glBindVertexArray(_vao[0]);

		// create a buffer name on the graphics card for each of the

		// until we get a normal std::map working, we'll just use call each
		// buffer by enumation
		//map<string, GLuint>::const_iterator vbo;
		//for (vbo=_vbo.begin(); vbo != _vbo.end(); vbo++)
		//{
		//	glGenBuffers(1, vbo->second);
		//}

		glGenBuffers(1, &_vbo[GLObject3D::POSITION]);
		//glGenBuffers(1, &_vbo[GLObject3D::COLOR]);
		glGenBuffers(1, &_vbo[GLObject3D::NORMAL]);

		// not using faces this go around ignore this boilerplate code
		//glGenBuffers(1, &_vbo[BufferType::FACE]);


		// Texture is not used currently, so I'm just commenting it out.
		//glGenBuffers(1, &_vbo[BufferType::TEXTURE];);


		// Here we copy the vertex data into our buffer on the card.  The parameters
	    // tell it the type of buffer object, the size of the data in bytes, the
	    // pointer for the data itself, and a hint for how we intend to use it.

		glBindBuffer(GL_ARRAY_BUFFER, _vbo[GLObject3D::POSITION]);
//	    glBufferData(GL_ARRAY_BUFFER, _positionVerts.size()*sizeof(vec4), _positionVerts.data(), GL_STATIC_DRAW);
	    glBufferData(GL_ARRAY_BUFFER, _positionVerts.size()*sizeof(vec4), &_positionVerts[0], GL_STATIC_DRAW);

		//glBindBuffer(GL_ARRAY_BUFFER, _vbo[GLObject3D::COLOR]);
		//glBufferData(GL_ARRAY_BUFFER, _colorVerts.size()*sizeof(vec4), _colorVerts.data(), GL_STATIC_DRAW);

		glBindBuffer(GL_ARRAY_BUFFER, _vbo[GLObject3D::NORMAL]);
//		glBufferData(GL_ARRAY_BUFFER, _normals.size()*sizeof(vec4), _normals.data(), GL_STATIC_DRAW);
		glBufferData(GL_ARRAY_BUFFER, _normals.size()*sizeof(vec4), &_normals[0], GL_STATIC_DRAW);

		//glBindBuffer(GL_ARRAY_BUFFER, _vbo[BufferType::FACE]);
		//glBufferData(GL_ARRAY_BUFFER, _normals.size()*sizeof(vec4), &_colorVerts[0], GL_STATIC_DRAW);

	}

	void GLObject3D::initBuffers(GLuint shaderprogram)
	{
		initBuffers();
		attachVertexPositionShader(shaderprogram);
		//attachVertexColorShader(shaderprogram);
		attachNormalShader(shaderprogram);
		initSelectionColor(shaderprogram);
	}

	void GLObject3D::makeActiveObject()
	{
		glBindVertexArray(_vao[0]);
	}

	uint GLObject3D::getbyteBufferSize()
	{
		return sizeof(vec4) * _quantityVerts;
	}
/*
	void GLObject3D::loadGLObjectFromFile()
	{
		ifstream myfile ("cubo.obj");
		while(!myfile.eof())
		{
			getline (myfile,line);
			if (line[0] == 'v')
			{
				myfile >> v >> valuesX[n]>> valuesY[n]>> valuesZ[n];
				cout << valuesX[n] << "\t" << valuesY[n] << "\t" << valuesZ[n] << endl;
				n++;
			}
		}
	return 0;
	}
*/
	void GLObject3D::loadGLObjectFromFile()
	{

	  ifstream in(_objectFilename.c_str(), ios::in);

	  if (!in)
	  {
		  cerr << "Cannot open " << _objectFilename << endl;
		  exit(1);
	  }

	  string line;

	  while (getline(in, line))
	  {
		// read in a 3D data for a point and add to normals list
	    if (line.substr(0,2) == "v ")
	    {
	      istringstream s(line.substr(2));
	      vec4* avect = new vec4();

	      s >> avect->x;
	      s >> avect->y;
	      s >> avect->z;
	      avect->w = 1.0f;

	      _rawPositionVerts.push_back(*avect);
//	      DEBUG_PRINT("_positionVerts.size() = %d", _positionVerts.size());
	    }
		// read in a 3D data for a normal and add to normals list
	    else if (line.substr(0,3) == "vn ")
	    {
	      istringstream s(line.substr(3));
	      vec4* avect = new vec4();

	      s >> avect->x;
	      s >> avect->y;
	      s >> avect->z;
	      avect->w = 0.0f;

	      _rawNormals.push_back(*avect);
	    }
		// read in a vector of face indecies and add to
		//faces list
	    else if (line.substr(0,2) == "f ")
	    {
			string subline = line.substr(2);
			boost::trim(subline);

			vector<string> faceVerticies;
			vector<string> faceVertexElements;
			uint vIndex;
			uint vtIndex;
			uint vnIndex;

			//DEBUG_PRINT("line=\"%s\"\n",subline.c_str());

			boost::split(faceVerticies,subline,boost::is_any_of(" "));

			//DEBUG_PRINT("faceVerticies.size()=%d\n",faceVerticies.size());

			for(uint i=0;i<faceVerticies.size();i++)
			{
				//DEBUG_PRINT("faceVerticies[%d]=\"%s\"\n",i,faceVerticies[i].c_str());
				//DEBUG_PRINT("faceVerticies[%d]=%s\n",i,faceVerticies[i].c_str());
				boost::split(faceVertexElements,faceVerticies[i],boost::is_any_of("/"));

				//DEBUG_PRINT("faceVertexElements[%d]=\"%s\"\n",i,faceVertexElements[i].c_str());

				vIndex = atoi(faceVertexElements[0].c_str());
				//DEBUG_PRINT("faceVertexElements[0]=\"%s\"\n",faceVertexElements[0].c_str());
				// copy faceElement to the output normals
				_positionVerts.push_back(_rawPositionVerts[vIndex-1]);

				// ignore this for now as is empty in the data files
				//vtIndex = atoi(faceVertexElements[1]);
				//DEBUG_PRINT("faceVertexElements[1]=\"%s\"\n",faceVertexElements[1].c_str());

				vnIndex = atoi(faceVertexElements[2].c_str());
				//DEBUG_PRINT("faceVertexElements[2]=\"%s\"\n",faceVertexElements[2].c_str());
				// copy faceElement to the output normals
				_normals.push_back(_rawNormals[vnIndex-1]);

			}

			// face indicies are 1-based
			// so we need to decrement to match the zero-based
			// vertex and normal arrays

			// copy faceElement to the output normals
			//_positionVerts.push_back(_rawPositionVerts[vIndex]);
			//_positionVerts.push_back(_rawPositionVerts[face->y]);
			//_positionVerts.push_back(_rawPositionVerts[face->z]);

			// copy faceElement to the output normals
			//_normals.push_back(_rawNormals[face->x]);
			//_normals.push_back(_rawNormals[face->y]);
			//_normals.push_back(_rawNormals[face->z]);

	    }
		// ignore comment line
	    else if (line[0] == '#')
	    {
		}
		// ignore comment line
	    else
	    {
	    }
	  }
	 in.close();
	}



	void GLObject3D::loadGLObjectFromFile(string filename)
	{
		this->_objectFilename = filename;
		loadGLObjectFromFile();
	}


	void GLObject3D::render()
	{
		// do nothing here. Virtual functions must be implemented
	}

	void GLObject3D::renderSelectionColor()
	{
	// do nothing here. Virtual functions must be implemented
	}

	bool GLObject3D::isSelected(GLubyte pixel[4])
	{
	// do nothing here. Virtual functions must be implemented
	}

	void GLObject3D::setDrawMode(GLenum mode)
	{
	// do nothing here. Virtual functions must be implemented
	}
	mat4 GLObject3D::transformModel(mat4 transform)
	{
	// do nothing here. Virtual functions must be implemented
	}


}

